﻿using System;
using System.Data;
using System.Collections.Generic;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;

namespace Project.Co_DataTransformationPrototype
{

    public class EventLogger : Logger
    {
        private static EventLogger instance;
        private const String ElementCreatedString = "Element Created";
        private const String ElementUpdatedString = "Element Updated";
        private const String ElementRemovedString = "Element Removed";
        private const String ElementSplittedString = "Element {0} Splitted its attribute here: ";
        private const String ElementMergeString = "Element {0} Merged its attribute to: ";
        private const String AttributeMergeString = "Attribute Merge: {0} is the new attribute ";
        private const String AttributeCreatedString = "Attribute Created: ";
        private const String AttributeMovedString = "Attribute Moved Here: ";
        private const String AttributeUpdatedString = "Attribute Updated: ";
        private const String AttributeRemovedString = "Attribute Removed: ";
        private const String LinkCreatedString = "Created link to: ";
        private const String LinkRemovedString = "Removed link with: ";
        private const String LinkUpdatedString = "Link's {0} was updated: ";
        private IDictionary<Guid, ElementEventInfo> eventCollection;

        public IDictionary<Guid, ElementEventInfo> EventCollection { get { return eventCollection; } }

        // Private Constructor
        private EventLogger()
        {
            this.eventCollection = new Dictionary<Guid, ElementEventInfo>();
        }

        public static EventLogger GetInstance()
        {
            if (instance == null)
                instance = new EventLogger();

            return instance;
        }

        #region Private utility methods
        /// <summary>
        /// Utility method to fill the event context
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="actionText"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        private EventContext fillEventContext(string entityName, string actionText, ElementEventArgs e)
        {
            EventContext eventContext = new EventContext();
            
            eventContext.entityName = entityName;
            eventContext.actionText = actionText;
            eventContext.timestamp = DateTime.Now;
            eventContext.elementEventArgs = e;

            return eventContext;
        }

        /// <summary>
        /// Utility method to handle collection change nofitications
        /// </summary>
        /// 
        private void handleChangeNotification(StateBase stateObject, MappingBase mappingObject)
        {
            // If tracking has been paused or application is in importing-mode, skip the event notification on attribute level (app. switches to transaction-level notifications)
            if (!instance.blnDisableChangeTracking)
            {
                eventCollection.AddElement(stateObject, mappingObject);
                OnCollectionChanged(eventCollection);
            }
        }
        #endregion

        #region Entity event consumers

        public override void elementAdded(object sender, ElementAddedEventArgs e)
        {
            EventContext eventContext = fillEventContext((e.ModelElement as Entity).Name, ElementCreatedString, e);
            handleChangeNotification(new AddedState(eventContext), new AdditionMapping(eventContext));
        }

        public override void elementRemoved(object sender, ElementDeletedEventArgs e)
        {
            EventContext eventContext = fillEventContext((e.ModelElement as Entity).CurrentName, ElementRemovedString, e);
            handleChangeNotification(new RemovedState(eventContext), new RemovalMapping(eventContext));
        }

        public override void elementUpdated(object sender, ElementPropertyChangedEventArgs e)
        {
            if (e.DomainProperty.Id == Entity.NameDomainPropertyId || e.DomainProperty.Id == Entity.CurrentNameDomainPropertyId)
            {
                string newValue = (string)e.NewValue;
                if (!String.IsNullOrEmpty((string)e.OldValue))
                {
                    if (!String.IsNullOrEmpty(newValue) && !newValue.Equals(e.OldValue))
                    {
                        EventContext eventContext = fillEventContext(newValue, ElementUpdatedString + " - Renamed from '" + (string)e.OldValue + "'", e);
                        handleChangeNotification(new RenamedState(eventContext), new RenameMapping(eventContext));
                    }
                }
            }
        }

        #endregion

        #region Relationship event consumers

        public override void linkAdded(object sender, ElementAddedEventArgs e)
        {
            EventContext eventContext = fillEventContext((e.ModelElement as EntityAssociation).Source.Name, LinkCreatedString + (e.ModelElement as EntityAssociation).Target.Name, e);
            handleChangeNotification(new AddedState(eventContext), new AdditionMapping(eventContext));
        }

        public override void linkRemoved(object sender, ElementDeletedEventArgs e)
        {
            EventContext eventContext = fillEventContext((e.ModelElement as EntityAssociation).Source.CurrentName, LinkRemovedString + (e.ModelElement as EntityAssociation).Target.CurrentName, e);
            handleChangeNotification(new RemovedState(eventContext), new RemovalMapping(eventContext));
        }

        public override void linkUpdated(object sender, ElementPropertyChangedEventArgs e)
        {
            if (e.DomainProperty.Id == EntityAssociation.DoCascadeDeleteDomainPropertyId || e.DomainProperty.Id == EntityAssociation.DoCascadeUpdateDomainPropertyId)
            {
                bool newValue = (bool)e.NewValue;
                if (!e.OldValue.Equals(newValue))
                {
                    EventContext eventContext = fillEventContext((e.ModelElement as EntityAssociation).Source.CurrentName, string.Format(LinkUpdatedString, "Cascade Mechanism") + e.DomainProperty.DisplayName + " - set to '" + newValue.ToString() + "'", e);
                    handleChangeNotification(new AddedState(eventContext), new CascadeUpdatedMapping(eventContext, newValue));
                }
            }
        }

        #endregion

        #region Attribute event consumers

        public override void attributeAdded(object sender, ElementAddedEventArgs e)
        {
            object sourceEntity;
            EventContext eventContext;

            if (e.TransactionContext.ContextInfo.TryGetValue(Operators.GetMoveOpTransactionKey, out sourceEntity))
            {
                eventContext = fillEventContext((e.ModelElement as EntityAttribute).Entity.Name, AttributeMovedString + (e.ModelElement as EntityAttribute).Name, e);
                handleChangeNotification(new MovedState(eventContext), new MoveMapping(eventContext, sourceEntity as Entity));
            }
            else if (e.TransactionContext.ContextInfo.TryGetValue(Operators.GetSplitOpTransactionKey, out sourceEntity))
            {
                eventContext = fillEventContext((e.ModelElement as EntityAttribute).Entity.Name, string.Format(ElementSplittedString, (sourceEntity as Entity).CurrentName) + (e.ModelElement as EntityAttribute).Name, e);
                handleChangeNotification(new MovedState(eventContext), new MoveMapping(eventContext, sourceEntity as Entity));
            }
            else if (e.TransactionContext.ContextInfo.TryGetValue(Operators.GetMergeOpTransactionKey, out sourceEntity))
            {
                eventContext = fillEventContext((e.ModelElement as EntityAttribute).Entity.Name, string.Format(ElementMergeString, (sourceEntity as Entity).CurrentName) + (e.ModelElement as EntityAttribute).Name, e);
                handleChangeNotification(new MovedState(eventContext), new MergeMapping(eventContext));
            }
            else if (e.TransactionContext.ContextInfo.TryGetValue(Operators.GetMergeOpAttributeTransactionKey, out sourceEntity))
            {
                eventContext = fillEventContext((e.ModelElement as EntityAttribute).Entity.Name, string.Format(AttributeMergeString, (e.ModelElement as EntityAttribute).Name), e);
                handleChangeNotification(new MovedState(eventContext), new MergeMapping(eventContext));
            }
            else
            {
                eventContext = fillEventContext((e.ModelElement as EntityAttribute).Entity.Name, AttributeCreatedString + (e.ModelElement as EntityAttribute).Name, e);
                handleChangeNotification(new AddedState(eventContext), new AdditionMapping(eventContext));
            }
        }

        public override void attributeRemoved(object sender, ElementDeletedEventArgs e)
        {
            EventContext eventContext = fillEventContext((e.ModelElement as EntityAttribute).ParentEntity.Name, AttributeRemovedString + (e.ModelElement as EntityAttribute).CurrentName, e);
            handleChangeNotification(new RemovedState(eventContext), new RemovalMapping(eventContext));
        }

        public override void attributeUpdated(object sender, ElementPropertyChangedEventArgs e)
        {
            Entity parentElement = null;
            EntityAttribute attribute = (e.ModelElement as EntityAttribute);

            if (attribute.IsDeleted)
                parentElement = attribute.ParentEntity;
            else
                parentElement = attribute.Entity;

            if (e.DomainProperty.Id == EntityAttribute.NameDomainPropertyId)
            {
                string newValue = (string)e.NewValue;
                if (!String.IsNullOrEmpty((string)e.OldValue) && !String.IsNullOrEmpty(newValue) && !newValue.Equals(e.OldValue))
                {
                    EventContext eventContext = fillEventContext(parentElement.Name, AttributeUpdatedString + (string)e.OldValue + " renamed to " + newValue, e);
                    handleChangeNotification(new RenamedState(eventContext), new RenameMapping(eventContext));
                }
            }
            else if (e.DomainProperty.Id == EntityAttribute.TypeDomainPropertyId)
            {
                SqlDbType newValue = (SqlDbType)e.NewValue;
                if (!newValue.Equals(e.OldValue))
                {
                    EventContext eventContext = fillEventContext(parentElement.Name, String.Format(AttributeUpdatedString + "{0} ({1}) changed its data type to ({2})", attribute.CurrentName, ((SqlDbType)e.OldValue).ToString(), newValue.ToString()), e);
                    handleChangeNotification(new AddedState(eventContext), new DataTypeChangedMapping(eventContext));
                }
            }
            else if (e.DomainProperty.Id == EntityAttribute.isPrimaryKeyDomainPropertyId)
            {
                string oldAttributeName;
                object primaryKeyReference;
                e.TransactionContext.ContextInfo.TryGetValue(Operators.GetPrimaryKeyRefTransactionKey, out primaryKeyReference);
                oldAttributeName = primaryKeyReference as string;

                EventContext eventContext = fillEventContext(parentElement.Name, AttributeUpdatedString + attribute.CurrentName + " - primary-key property set to: " + ((bool)e.NewValue).ToString(), e);
                handleChangeNotification(new KeyUpdatedState(eventContext), new KeyUpdatedMapping(eventContext, true, ((bool)e.NewValue), null, null, oldAttributeName));
            }
            else if (e.DomainProperty.Id == EntityAttribute.isForeignKeyDomainPropertyId)
            {
                string oldEntityName, oldAttributeName;
                object foreignKeyReference, foreignKeyRefEString, foreignKeyRefAString;
                e.TransactionContext.ContextInfo.TryGetValue(Operators.GetForeignKeyRefTransactionKey, out foreignKeyReference);
                e.TransactionContext.ContextInfo.TryGetValue(Operators.GetFKEntityOldNameTransactionKey, out foreignKeyRefEString);
                e.TransactionContext.ContextInfo.TryGetValue(Operators.GetFKAttributeOldNameTransactionKey, out foreignKeyRefAString);
                oldEntityName = foreignKeyRefEString as string;
                oldAttributeName = foreignKeyRefAString as string;

                EventContext eventContext = fillEventContext(parentElement.Name, AttributeUpdatedString + attribute.CurrentName + " - foreign-key property set to: " + ((bool)e.NewValue).ToString(), e);
                handleChangeNotification(new KeyUpdatedState(eventContext), new KeyUpdatedMapping(eventContext, false, ((bool)e.NewValue), foreignKeyReference as Entity, oldEntityName, oldAttributeName));
            }
        }

        #endregion

        #region Transaction event consumer

        public override void transactionCommited(object sender, TransactionCommitEventArgs e)
        {
            OnCollectionChanged(eventCollection);
        }

        public override void transactionRolledback(object sender, TransactionRollbackEventArgs e)
        {
            OnCollectionChanged(eventCollection);
        }

        #endregion
    }
}
